// CoreCalc, a spreadsheet core implementation
// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Diagnostics;
using C5;

namespace CoreCalc.SheetDefinedFunctions
{
  public abstract class CGExpr : CodeGenerate, ICompilable, IDepend
  {
    /// <summary>
    ///  The entry point for compilation, called from ProgramLines.  
    ///  The generated code must leave a Value on the stack top.
    /// </summary>
    public abstract void Compile();

    /// <summary>
    /// Compile expression that is expected to evaluate to a number, leaving 
    /// a float64 on the stack top and avoiding wrapping where possible.  
    /// If result is an error, produce a NaN whose 32 least significant bits
    /// give that error's int index into the ErrorValue.errorTable arraylist.
    /// </summary>
    public abstract void CompileToDoubleOrNan();

    // General version in terms of CompileToDoubleOrNan.  
    // Should be overridden in CGNumberConst, CGTextConst, CGError, CGComposite subclasses, ...
    /// <summary>
    /// Compile expression that is expected to evaluate to a proper (finite 
    /// and non-NaN) number; generate code to test whether it is actually a 
    /// proper number and then execute the code generated by ifProper, or 
    /// else execute the code generated by ifOther.
    /// </summary>
    /// <param name="ifProper">Generates code for the case where the expression 
    /// evaluates to a proper number; the generated code expects to find the value as
    /// an unwrapped proper float64 on the stack top.</param>
    /// <param name="ifOther"></param>
    public virtual void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      CompileToDoubleOrNan();
      ilg.Emit(OpCodes.Stloc, testDouble);
      ilg.Emit(OpCodes.Ldloc, testDouble);
      ilg.Emit(OpCodes.Call, isInfinityMethod);
      ilg.Emit(OpCodes.Brtrue, ifOther.GetLabel(ilg));
      ilg.Emit(OpCodes.Ldloc, testDouble);
      ilg.Emit(OpCodes.Call, isNaNMethod);
      ilg.Emit(OpCodes.Brtrue, ifOther.GetLabel(ilg));
      ilg.Emit(OpCodes.Ldloc, testDouble);
      ifProper.Gen(ilg);
      if (!ifOther.Generated)
      {
        Label endLabel = ilg.DefineLabel();
        ilg.Emit(OpCodes.Br, endLabel);
        ifOther.Gen(ilg);
        ilg.MarkLabel(endLabel);
      }
    }

    /// <summary>
    /// Compiles an expression as a condition, that can be true (if non-zero) or 
    /// false (if zero) or other (if +/-infinity or NaN).  If possible, avoids 
    /// computing and pushing a value and then testing it, instead performing 
    /// comparisons directly on arguments, or even statically.  This implementation 
    /// is a general version in terms of CompileToDoubleProper.  Should be overridden 
    /// in CGNumberConst, CGTextConst, CGError, CGIf, CGComparison, ...
    /// </summary>
    /// <param name="ifTrue">Generates code for the true branch</param>
    /// <param name="ifFalse">Generates code for the false branch</param>
    /// <param name="ifOther">Generates code for the other (neither true nor 
    /// false) branch</param>
    public virtual void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      CompileToDoubleProper(
        new Generate(delegate
      {
        ilg.Emit(OpCodes.Ldc_R8, 0.0);
        ilg.Emit(OpCodes.Beq, ifFalse.GetLabel(ilg));
        ifTrue.Gen(ilg);
        if (!ifFalse.Generated)
        {
          Label endLabel = ilg.DefineLabel();
          ilg.Emit(OpCodes.Br, endLabel);
          ifFalse.Gen(ilg);
          ilg.MarkLabel(endLabel);
        }
      }),
      ifOther);
    }

    /// <summary>
    /// Update the evaluation conditions (in the evalConds dictionary) for every cell
    /// referenced from this expression, assuming that this expression itself 
    /// has evaluation condition evalCond.
    /// </summary>
    /// <param name="evalCond"></param>
    /// <param name="evalConds"></param>
    public abstract void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr,PathCond> evalConds);

    /// <summary>
    /// Called on a sheet-defined function's output 
    /// cell expression to record that that expression and some of its
    /// subexpressions are in tail position.  Only overridden in CGIf, 
    /// CGChoose, CGSdfCall and CGApply.
    /// </summary>
    public virtual void NoteTailPosition() { }
    
    /// <summary>
    /// Returns true if the expression's evaluation could 
    /// be recursive, have side effects, or take a long time.
    /// </summary>
    /// <param name="bound">The bound on non-serious expression size</param>
    public virtual bool IsSerious(ref int bound)
    {
      return 0 > bound--;
    }

    public abstract void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn);

    public abstract StrictCellTypeHolder GetStrictCells();
    public abstract Typ Type();

    public static bool IsZero(CGExpr e) {
      return e is CGNumberConst && (e as CGNumberConst).number.value == 0.0;
    }
  }

  // ----------------------------------------------------------------

  public class CGCellRef : CGExpr
  {
    private readonly FullCellAddr cellAddr;

    public CGCellRef(FullCellAddr cellAddr)
    {
      this.cellAddr = cellAddr;
    }

    public override void Compile()
    {
      Variable var = CellReferences[cellAddr];
      var.EmitLoad(ilg);
      if (var.Type == Typ.Number)
        WrapDoubleToNumberValue();
      // In other cases, there's no need to wrap the variable's contents
    }

    public override void CompileToDoubleOrNan()
    {
      Variable doubleVar;
      if (NumberVariables.TryGetValue(cellAddr, out doubleVar))
        doubleVar.EmitLoad(ilg);
      else
      {
        Variable var = CellReferences[cellAddr];
        if (var.Type == Typ.Value)
        {
          var.EmitLoad(ilg);
          UnwrapToDoubleOrNan();
        }
        else if (var.Type == Typ.Number)
          var.EmitLoad(ilg);          
        else // A variable of a type not convertible to a float64, so ArgTypeError
          LoadArgTypeErrorNan();
      }
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      PathCond old;
      if (evalConds.Find(cellAddr, out old))
        evalConds[cellAddr] = old.Or(evalCond);
      else
        evalConds[cellAddr] = evalCond;
    }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      dependsOn(cellAddr);
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      return new StrictCellTypeHolder(cellAddr);
    }

    public override string ToString()
    {
      return CellReferences[cellAddr].Name;
    }

    public override Typ Type()
    {
      return CellReferences[cellAddr].Type;
    }
  }

  // ----------------------------------------------------------------

  public class CGCachedExpr : CGExpr
  {
    public readonly CGExpr expr;
    private readonly CachedAtom cachedAtom;
    private LocalBuilder cacheVariable;  // The cache localvar (double) 
    private int generateCount = 0;       // Number of times emitted by CachedAtom.ToCGExpr
    private readonly int cacheNumber;    // Within the current SDF, for diagnostics

    // All the current SDF's cached expressions:
    private static ArrayList<CGCachedExpr> caches; 

    private const int uninitializedBits = -1;
    private static readonly double uninitializedNan = ErrorValue.MakeNan(uninitializedBits);
    private static readonly MethodInfo
      doubleToInt64BitsMethod = typeof(System.BitConverter).GetMethod("DoubleToInt64Bits");

    public CGCachedExpr(CGExpr expr, CachedAtom cachedAtom)
    {
      this.expr = expr;
      this.cachedAtom = cachedAtom;
      this.cacheNumber = caches.Count;
      caches.Add(this);
    }

    public static void Reset()
    {
      caches = new ArrayList<CGCachedExpr>();
    }

    public override void Compile()
    {
      CompileToDoubleOrNan();
      ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
    }

    public override void CompileToDoubleOrNan()
    {
      if (IsCacheNeeded)
        EmitCacheAccess();
      else
        expr.CompileToDoubleOrNan();
    }

    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      if (IsCacheNeeded)
        // Call CompileToDoubleProper via base class CompileCondition
        base.CompileCondition(ifTrue, ifFalse, ifOther);
      else
        expr.CompileCondition(ifTrue, ifFalse, ifOther);
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      if (IsCacheNeeded)
        // Call CompileToDoubleOrNan via base class CompileToDoubleProper
        base.CompileToDoubleProper(ifProper, ifOther);
      else
        expr.CompileToDoubleProper(ifProper, ifOther);
    }

    public void IncrementGenerateCount()
    {
      generateCount++;
    }

    // Cache only if used in PathCond and worth caching
    private bool IsCacheNeeded
    {
      get { return generateCount > 0 && !(expr is CGCellRef) && !(expr is CGConst); }
    }

    /// <summary>
    /// Emit cache initialization for all expressions that need it.
    /// Must be called (by ...) before any cache is actually used.
    /// </summary>
    public static void EmitCacheInitializations()
    {
      foreach (CGCachedExpr cache in caches)
        if (cache.IsCacheNeeded)
          cache.EmitCacheInitialization();
    }

    /// <summary>
    /// This is called, at most once, before any use of the cache is generated.
    /// </summary>
    private void EmitCacheInitialization()
    {
      Debug.Assert(cacheVariable == null);  // Has not been called before
      cacheVariable = ilg.DeclareLocal(typeof(double));
      Console.WriteLine("Emitted {0} in localvar {1}", this, cacheVariable);
      ilg.Emit(OpCodes.Ldc_R8, uninitializedNan);
      ilg.Emit(OpCodes.Stloc, cacheVariable);
    }

    /// <summary>
    /// Generate code for each use of the cache.
    /// </summary>
    private void EmitCacheAccess()
    {
      Debug.Assert(cacheVariable != null); // EmitCacheInitialization() has been called
      ilg.Emit(OpCodes.Ldloc, cacheVariable);
      ilg.Emit(OpCodes.Call, isNaNMethod);
      Label endLabel = ilg.DefineLabel();
      ilg.Emit(OpCodes.Brfalse, endLabel);  // Already computed, non-NaN result
      ilg.Emit(OpCodes.Ldloc, cacheVariable);
      ilg.Emit(OpCodes.Call, doubleToInt64BitsMethod);
      ilg.Emit(OpCodes.Conv_I4);
      ilg.Emit(OpCodes.Ldc_I4, uninitializedBits);
      ilg.Emit(OpCodes.Ceq);
      ilg.Emit(OpCodes.Brfalse, endLabel);  // Already computed, NaN result
      expr.CompileToDoubleOrNan();
      // ilg.EmitWriteLine("Filled " + this);
      ilg.Emit(OpCodes.Stloc, cacheVariable);
      ilg.MarkLabel(endLabel);
      ilg.Emit(OpCodes.Ldloc, cacheVariable);
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      throw new ImpossibleException("CGCachedExpr.EvalCond");
    }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      expr.DependsOn(here, dependsOn);
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      // sestoft: ??
      return new StrictCellTypeHolder(false);
    }

    public override string ToString()
    {
      return "CACHE#" + cacheNumber + "[" + expr + "]";
    }

    public override Typ Type()
    {
      return Typ.Number;
    }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A CGComposite represents an arithmetic operation, a call to a 
  /// built-in "function" (including IF, AND, OR, RAND, LN, EXP, ...) 
  /// a call to a sheet-defined or external function, and more.
  /// </summary>
  public abstract class CGComposite : CGExpr
  {
    protected readonly CGExpr[] es;

    protected CGComposite(CGExpr[] es)
    {
      this.es = es;
    }

    protected abstract Typ GetInputTypWithoutLengthCheck(int pos);

    public Typ GetInputTyp(int pos)
    {
      if (pos < 0 || pos >= Arity)
        throw new InvalidOperationException("Index out of bounds");
      return GetInputTypWithoutLengthCheck(pos);
    }

    public override bool IsSerious(ref int bound)
    {
      bool serious = base.IsSerious(ref bound);
      for (int i = 0; !serious && i < es.Length; i++)
        serious = es[i].IsSerious(ref bound);
      return serious;
    }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      foreach (CGExpr e in es)
        e.DependsOn(here, dependsOn);
    }

    protected String FormatAsCall(String name)
    {
      StringBuilder sb = new StringBuilder();
      sb.Append(name).Append("(");
      if (es.Length > 0)
        sb.Append(es[0]);
      for (int i = 1; i < es.Length; i++)
        sb.Append(",").Append(es[i]);
      return sb.Append(")").ToString();
    }

    public abstract int Arity { get; }
  }

  // ----------------------------------------------------------------
  
  public class CGIf : CGComposite
  {
    public CGIf(CGExpr[] es) : base(es) { }

    // This handles compilation of IF(e0,e1,e2) in a Value context
    public override void Compile()
    {
      if (es.Length != 3)
        LoadError(ErrorValue.argCountErrorValueIndex);
      else
        es[0].CompileCondition(
          new Generate(delegate { es[1].Compile(); }),
          new Generate(delegate { es[2].Compile(); }),
          new Generate(delegate
        {
          ilg.Emit(OpCodes.Ldloc, testDouble);
          WrapDoubleToNumberValue();
        }));
    }

    // This handles compilation of 5 + IF(e0,e1,e2)
    public override void CompileToDoubleOrNan()
    {
      if (es.Length != 3)
        LoadError(ErrorValue.argCountErrorValueIndex);
      else
        es[0].CompileCondition(
          new Generate(delegate { es[1].CompileToDoubleOrNan(); }),
          new Generate(delegate { es[2].CompileToDoubleOrNan(); }),
          new Generate(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    // This handles compilation of 5 > IF(e0,e1,e2)
    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      if (es.Length != 3)
      {
        SetArgCountErrorNan();
        ifOther.Gen(ilg);
      }
      else
        es[0].CompileCondition(
          new Generate(delegate { es[1].CompileToDoubleProper(ifProper, ifOther); }),
          new Generate(delegate { es[2].CompileToDoubleProper(ifProper, ifOther); }),
          ifOther);
    }

    // This handles compilation of IF(IF(e00,e01,e02), e1, e2) 
    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      if (es.Length != 3)
      {
        SetArgCountErrorNan();
        ifOther.Gen(ilg);
      }
      else
        es[0].CompileCondition(
        new Generate(delegate { es[1].CompileCondition(ifTrue, ifFalse, ifOther); }),
        new Generate(delegate { es[2].CompileCondition(ifTrue, ifFalse, ifOther); }),
        ifOther);
    }
    
    public override string ToString()
    {
      return FormatAsCall("IF");      
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      if (es.Length == 3)
      {
        CachedAtom atom = new CachedAtom(es[0]);
        es[0].EvalCond(evalCond, evalConds);
        es[0] = atom.cachedExpr;
        es[1].EvalCond(evalCond.And(atom), evalConds);
        es[2].EvalCond(evalCond.AndNot(atom), evalConds);
      }
    }

    public override void NoteTailPosition()
    {
      if (es.Length == 3)
      {
        es[1].NoteTailPosition();
        es[2].NoteTailPosition();
      }
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      if (es.Length == 3)
      {
        StrictCellTypeHolder back = es[1].GetStrictCells();
        // sestoft: Seems wrong, this should be Intersection, not Union:
        back.Union(es[2].GetStrictCells());
        // sestoft: What's this:
        back.InsertAllAsNonResults(es[0].GetStrictCells());
        return back;
      } else // sestoft: fake
      return new StrictCellTypeHolder(false);
    }

    public override Typ Type()
    {
      if (es.Length != 3)
        return Typ.Error;
      else
        return Lub(es[1].Type(), es[2].Type());
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      if (pos == 0)
        return Typ.Number;
      else
        return Typ.Value;
    }

    public override int Arity { get { return 3; } }
  }

  // ----------------------------------------------------------------
  
  public class CGChoose : CGComposite
  {
    public CGChoose(CGExpr[] es) : base(es) { }

    public override void Compile()
    {
      es[0].CompileToDoubleProper(new Generate(delegate {
        Label endLabel = ilg.DefineLabel();
        Label[] labels = new Label[es.Length - 1];
        for (int i = 1; i < es.Length; i++)
          labels[i - 1] = ilg.DefineLabel();
        ilg.Emit(OpCodes.Conv_I4);
        ilg.Emit(OpCodes.Ldc_I4, 1);
        ilg.Emit(OpCodes.Sub);
        ilg.Emit(OpCodes.Switch, labels);
        LoadError("Value in CHOOSE");
        for (int i = 1; i < es.Length; i++)
        {
          ilg.Emit(OpCodes.Br, endLabel);
          ilg.MarkLabel(labels[i - 1]);
          es[i].Compile();
        }
        ilg.MarkLabel(endLabel);
      }),
      new Generate(delegate { LoadError("Value in CHOOSE"); })
      );
    }

    public override void CompileToDoubleOrNan()
    {
      es[0].CompileToDoubleProper(new Generate(delegate
      {
        Label endLabel = ilg.DefineLabel();
        Label[] labels = new Label[es.Length - 1];
        for (int i = 1; i < es.Length; i++)
          labels[i - 1] = ilg.DefineLabel();
        ilg.Emit(OpCodes.Conv_I4);
        ilg.Emit(OpCodes.Ldc_I4, 1);
        ilg.Emit(OpCodes.Sub);
        ilg.Emit(OpCodes.Switch, labels);
        LoadErrorNan("Value in CHOOSE");
        for (int i = 1; i < es.Length; i++)
        {
          ilg.Emit(OpCodes.Br, endLabel);
          ilg.MarkLabel(labels[i - 1]);
          es[i].CompileToDoubleOrNan();
        }
        ilg.MarkLabel(endLabel);
      }),
      new Generate(delegate { LoadErrorNan("Value in CHOOSE"); })
      );
    }

    public override string ToString()
    {
      return FormatAsCall("CHOOSE");
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      if (es.Length >= 1)
      {
        CachedAtom atom = new CachedAtom(es[0]);
        CGCachedExpr cached = atom.cachedExpr;
        es[0].EvalCond(evalCond, evalConds);
        es[0] = cached;
        for (int i = 1; i < es.Length; i++)
        {
          CGNumberConst iConst = new CGNumberConst(new NumberValue(i));
          CGExpr cond = new CGEqual(new CGExpr[] { cached, iConst });
          es[i].EvalCond(evalCond.And(new CachedAtom(cond)), evalConds);
        }
      }
    }

    public override void NoteTailPosition()
    {
      for (int i = 1; i < es.Length; i++)
        es[i].NoteTailPosition();
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      // sestoft: probably wrong
      StrictCellTypeHolder back = es[0].GetStrictCells();
      return back;
    }

    public override Typ Type()
    {
      return Typ.Value;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      if (pos == 0)
        return Typ.Number;
      else
        return Typ.Value;
    }

    public override int Arity { get { return 3; } }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A CGAnd object represents a variadic AND operation, strict only
  /// in its first argument -- although that's not Excel semantics.
  /// </summary>
  public class CGAnd : CGComposite
  {
    public CGAnd(CGExpr[] es) : base(es) { }

    public override void Compile()
    {
      CompileCondition(
        new Generate(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.oneField); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.zeroField); }),
        GenLoadError()
        );
    }

    public override void CompileToDoubleOrNan()
    {
      CompileCondition(
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      CompileCondition(
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); ifProper.Gen(ilg); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); ifProper.Gen(ilg); }),
        ifOther);
    }

    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      for (int i = es.Length - 1; i >= 0; i--)
      {
        // These declarations are needed to capture rvalues rather than lvalues:
        CGExpr ei = es[i];
        Generate localIfTrue = ifTrue;
        ifTrue = new Generate(delegate { ei.CompileCondition(localIfTrue, ifFalse, ifOther); });
      }
      ifTrue.Gen(ilg);
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      for (int i = 0; i < es.Length; i++)
      {
        es[i].EvalCond(evalCond, evalConds);
        if (SHORTCIRCUIT_EVALCONDS && i != es.Length - 1)
        {
          // Take short-circuit evaluation into account for precision
          CachedAtom atom = new CachedAtom(es[i]);
          evalCond = evalCond.And(atom);
          es[i] = atom.cachedExpr;
        }
      }
    }

    public override string ToString()
    {
      return FormatAsCall("AND");
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      if (es.Length > 0)
        return es[0].GetStrictCells();
      else
        return new StrictCellTypeHolder(false);
    }

    public override Typ Type()
    {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Number;
    }

    public override int Arity { get { return es.Length; } }
  }

  // ----------------------------------------------------------------

  public class CGOr : CGComposite
  {
    public CGOr(CGExpr[] es) : base(es) { }

    public override void Compile()
    {
      CompileCondition(
        new Generate(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.oneField); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldsfld, NumberValue.zeroField); }),
        GenLoadError()
        );
    }

    public override void CompileToDoubleOrNan()
    {
      CompileCondition(
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      CompileCondition(
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); ifProper.Gen(ilg); }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); ifProper.Gen(ilg); }),
        ifOther);
    }

    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      for (int i = es.Length - 1; i >= 0; i--)
      {
        // These declarations are needed to capture rvalues rather than lvalues:
        CGExpr ei = es[i];
        Generate localIfFalse = ifFalse;
        ifFalse = new Generate(delegate { ei.CompileCondition(ifTrue, localIfFalse, ifOther); });
      }
      ifFalse.Gen(ilg);
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      for (int i = 0; i < es.Length; i++)
      {
        es[i].EvalCond(evalCond, evalConds);
        if (SHORTCIRCUIT_EVALCONDS && i != es.Length - 1)
        {
          // Take short-circuit evaluation into account for precision
          CachedAtom atom = new CachedAtom(es[i]);
          evalCond = evalCond.AndNot(atom);
          es[i] = atom.cachedExpr;
        }
      }
    }

    public override string ToString()
    {
      return FormatAsCall("OR"); 
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      if (es.Length > 0)
        return es[0].GetStrictCells();
      else
        return new StrictCellTypeHolder(false);
    }

    public override Typ Type()
    {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Number;
    }

    public override int Arity { get { return es.Length; } }
  }

  // ----------------------------------------------------------------

  public abstract class CGStrictOperation : CGComposite
  {
    public CGStrictOperation(CGExpr[] es) : base(es) { }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    {
      for (int i = 0; i < es.Length; i++)
        es[i].EvalCond(evalCond, evalConds);
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      if (es.Length == 0)
        return new StrictCellTypeHolder(false);

      StrictCellTypeHolder first = es[0].GetStrictCells();

      // sestoft: This seems to assign the "expected" type GetInputTyp(i) 
      // to each of the variables in the set expressions[i].GetStrictCells();
      // but is this really correct?  
      for (int i = 1; i < es.Length; i++)
      {
        // sestoft: Let's see if removing this makes any difference.  Turns out it makes 
        // NORMDISTCDF and INVNORMDISTCDF introduce a few extra isinst NumberValue tests.
        first.Union(es[i].GetStrictCells(), GetInputTyp(i));
      }

      return first;
    }
  }

  // ---------------------------------------------------------------

  public class CGFunction : CGStrictOperation
  {
    private readonly FunctionInfo functionInfo;

    public CGFunction(FunctionInfo functionInfo, CGExpr[] es)
      : base(es)
    {
      this.functionInfo = functionInfo;
    }

    private static readonly Signature
      unitToNum = new Signature(Typ.Number),
      numToNum = new Signature(Typ.Number, Typ.Number),
      numNumToNum = new Signature(Typ.Number, Typ.Number, Typ.Number),
      arrayToNum = new Signature(Typ.Number, Typ.Array),
      valueToNum = new Signature(Typ.Number, Typ.Value),
      valueToValue = new Signature(Typ.Value, Typ.Value),
      valueValueToValue = new Signature(Typ.Value, Typ.Value, Typ.Value),
      valueValueValueToValue = new Signature(Typ.Value, Typ.Value, Typ.Value, Typ.Value),
      arrayNumNumToValue = new Signature(Typ.Value, Typ.Array, Typ.Number, Typ.Number),
      arrayNumNumNumNumToValue = new Signature(Typ.Value, Typ.Array, Typ.Number, Typ.Number, Typ.Number, Typ.Number);

    private static readonly FunctionInfo
      absFunction = MakeMath1("Abs"),
      asinFunction = MakeMath1("Asin"),
      acosFunction = MakeMath1("Acos"),
      atanFunction = MakeMath1("Atan"),
      atan2Function = MakeFunction("ATAN2", "ExcelAtan2", numNumToNum, false),
      averageFunction = MakeFunction("AVERAGE", "Average", null, true),
      cArrayFunction = MakeFunction("CARRAY", "CArray", valueValueValueToValue, true),
      ceilingFunction = MakeFunction("CEILING", "ExcelCeiling", numNumToNum, false),
      colMapFunction = MakeFunction("COLMAP", "ColMap", valueValueToValue, true),
      columnsFunction = new FunctionInfo("Columns", ArrayValue.columnsMethod, arrayToNum, false),
      concatFunction = MakeFunction("&", "ExcelConcat", valueValueToValue, false),
      cosFunction = MakeMath1("Cos"),
      countIfFunction = MakeFunction("COUNTIF", "CountIf", valueValueToValue, true),
      expFunction = MakeMath1("Exp"),
      floorFunction = MakeFunction("FLOOR", "ExcelFloor", numNumToNum, false),
      hArrayFunction = MakeFunction("HARRAY", "HArray", null, true),
      hCatFunction = MakeFunction("HCAT", "HCat", null, true),
      indexFunction = new FunctionInfo("Index", ArrayValue.indexMethod, arrayNumNumToValue, false),
      isArrayFunction = MakeFunction("ISARRAY", "IsArray", valueToNum, false),
      isErrorFunction = MakeFunction("ISERROR", "IsError", valueToNum, false),
      lnFunction = MakeMath1("Log"),
      log10Function = MakeMath1("Log10"),
      mapFunction = MakeFunction("MAP", "Map", valueValueToValue, true),
      maxFunction = MakeFunction("MAX", "Max", null, true),
      minFunction = MakeFunction("MIN", "Min", null, true),
      modFunction = MakeFunction("MOD", "ExcelMod", numNumToNum, false),
      nowFunction = MakeFunction("NOW", "ExcelNow", unitToNum, false),
      powFunction = MakeFunction("POW", "ExcelPow", numNumToNum, false),
      randomFunction = MakeFunction("RAND", "ExcelRand", unitToNum, false),
      reduceFunction = MakeFunction("REDUCE", "Reduce", valueValueValueToValue, true),
      roundFunction = MakeFunction("ROUND", "ExcelRound", numNumToNum, false),
      rowMapFunction = MakeFunction("ROWMAP", "RowMap", valueValueToValue, true),
      rowsFunction = new FunctionInfo("Rows", ArrayValue.rowsMethod, arrayToNum, false),
      sinFunction = MakeMath1("Sin"),
      sliceFunction = new FunctionInfo("Slice", ArrayValue.sliceMethod, arrayNumNumNumNumToValue, false),
      sqrtFunction = MakeMath1("Sqrt"),
      sumFunction = MakeFunction("SUM", "Sum", null, true),
      sumIfFunction = MakeFunction("SUMIF", "SumIf", valueValueToValue, true),
      tabulateFunction = MakeFunction("TABULATE", "Tabulate", valueValueValueToValue, true),
      tanFunction = MakeMath1("Tan"),
      transposeFunction = MakeFunction("TRANSPOSE", "Transpose", valueToValue, true),
      vArrayFunction = MakeFunction("VARRAY", "VArray", null, true),
      vCatFunction = MakeFunction("VCAT", "VCat", null, true);

    public static CGExpr Make(String name, CGExpr[] es)
    {
      name = name.ToUpper();
      // This switch should agree with the function table in Functions.cs
      switch (name)
      {
        case "+": return new CGArithmetic2(OpCodes.Add, '+', es);
        case "*": return new CGArithmetic2(OpCodes.Mul, '*', es);
        case "-": return new CGArithmetic2(OpCodes.Sub, '-', es);
        case "/": return new CGArithmetic2(OpCodes.Div, '/', es);
        case "^": return new CGFunction(powFunction, es);

        case "&": return new CGFunction(concatFunction, es);

        case "=": return CGEqual.Make(es);
        case "<>": return CGNotEqual.Make(es);
        case ">": return new CGGreaterThan(es);
        case "<=": return new CGLessThanOrEqual(es);
        case "<": return new CGLessThan(es);
        case ">=": return new CGGreaterThanOrEqual(es);

        // Non-strict or other special treatment
        case "AND": return new CGAnd(es);
        case "APPLY": return new CGApply(es);
        case "AVERAGE": return new CGValuesToNumFunction(averageFunction, es);
        case "CHOOSE": return new CGChoose(es);
        case "ERR": return new CGError(es);
        case "EXTERN": return new CGExtern(es);
        case "GETFUN": return new CGGetfun(es);
        case "HARRAY": return new CGValuesToValueFunction(hArrayFunction, es);
        case "HCAT": return new CGValuesToValueFunction(hCatFunction, es);
        case "IF": return new CGIf(es);
        case "LOG":
        case "LOG10": return new CGFunction(log10Function, es);
        case "LN": return new CGFunction(lnFunction, es);
        case "MAX": return new CGValuesToNumFunction(maxFunction, es);
        case "MIN": return new CGValuesToNumFunction(minFunction, es);
        case "NEG": return new CGNeg(es);
        case "NOT": return new CGNot(es);
        case "OR": return new CGOr(es);
        case "PI":
          if (es.Length == 0)
            return new CGNumberConst(NumberValue.PI);
          else
            return new CGError(ErrorValue.argCountErrorValueIndex);
        case "SUM": return new CGValuesToNumFunction(sumFunction, es);
        case "VARRAY": return new CGValuesToValueFunction(vArrayFunction, es);
        case "VCAT": return new CGValuesToValueFunction(vCatFunction, es);
        default:
          // The general case for most built-in functions
          FunctionInfo functionInfo;
          if (FunctionInfo.Find(name, out functionInfo))
            return new CGFunction(functionInfo, es);
          else
          { // May be a sheet-defined function
            SdfInfo sdfInfo = SdfManager.GetInfo(name);
            if (sdfInfo != null)
              return new CGSdfCall(sdfInfo, es);
            else
              return new CGError(ErrorValue.noSuchFunctionErrorValueIndex);
          }
      }
    }

    private static FunctionInfo MakeMath1(String name)
    {
      return new FunctionInfo(name.ToUpper(),
        typeof(Math).GetMethod(name, new Type[] { typeof(double) }),
        numToNum,
        false /* not serious */);
    }

    private static FunctionInfo MakeFunction(String ssName, String functionName, 
                                             Signature signature, bool isSerious)
    {
      return new FunctionInfo(ssName, 
        Function.type.GetMethod(functionName),
        signature, 
        isSerious);
    }

    public override void Compile()
    {
      if (es.Length != Arity)
        LoadError(ErrorValue.argCountErrorValueIndex);
      else
      {
        Generate success;
        if (functionInfo.signature.retType == Typ.Number)
          success =
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
              ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
            });
        else // Everything else is represented by a subclass of Value
          success =
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
            });
        // TODO: This should probably load error from testValue instead?
        Generate ifOther = GenLoadError(ErrorValue.argTypeErrorValueIndex);
        CompileArgumentsAndApply(es, success, ifOther);
      }
    }

    public override void CompileToDoubleOrNan()
    {
      if (es.Length != Arity)
        ilg.Emit(OpCodes.Ldc_R8, ErrorValue.argCountErrorValueNan);
      else
      {
        Generate success;
        if (functionInfo.signature.retType == Typ.Number)
          success =
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
            });
        else // Else try to convert Value to double
          success =
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
              ilg.Emit(OpCodes.Call, Value.toDoubleOrNanMethod);
            });
        Generate ifOther = new Generate(delegate { LoadArgTypeErrorNan(); });
        CompileArgumentsAndApply(es, success, ifOther);
      }
    }

    // Generate code to evaluate all argument expressions, including the receiver es[1]
    // if the method is an instance method, and convert their values to .NET types.

    private void CompileArgumentsAndApply(CGExpr[] es, Generate ifSuccess, Generate ifOther)
    {
      int argCount = es.Length;
      // The error continuations must pop the arguments computed so far.
      Generate[] errorCont = new Generate[argCount];
      if (argCount > 0)
        errorCont[0] = ifOther;
      for (int i = 1; i < argCount; i++)
      {
        int ii = i; // Capture lvalue -- do NOT inline!
        errorCont[ii] = new Generate(delegate
        {
          ilg.Emit(OpCodes.Pop);
          errorCont[ii - 1].Gen(ilg);
        });
      }
      // Generate code, backwards, to evaluate argument expressions and
      // convert to external method's argument types
      for (int i = argCount - 1; i >= 0; i--)
      {
        // These local vars capture rvalue rather than lvalue -- do NOT inline them!
        CGExpr ei = es[i];
        Generate localSuccess = ifSuccess;
        Typ argType = functionInfo.signature.argTypes[i];
        Generate ifError = errorCont[i];
        if (argType == Typ.Number)
          ifSuccess = new Generate(delegate
             {
               ei.CompileToDoubleOrNan();
               localSuccess.Gen(ilg);
             });
        else if (argType == Typ.Function)
          ifSuccess = new Generate(delegate
             {
               ei.Compile();
               CheckType(FunctionValue.type, localSuccess, ifError);
             });
        else if (argType == Typ.Array)
          ifSuccess = new Generate(delegate
             {
               ei.Compile();
               CheckType(ArrayValue.type, localSuccess, ifError);
             });
        else if (argType == Typ.Text)
          ifSuccess = new Generate(delegate
            {
              ei.Compile();
              CheckType(TextValue.type, localSuccess, ifError);
            });
        else // argType.Value -- TODO: neglects to propagate ErrorValue from argument
          ifSuccess = new Generate(delegate
            {
              ei.Compile();
              localSuccess.Gen(ilg);
            });
      }
      ifSuccess.Gen(ilg);
    }

    public override bool IsSerious(ref int bound)
    {
      return functionInfo.isSerious || base.IsSerious(ref bound);
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return functionInfo.signature.argTypes[pos];
    }

    public override Typ Type()
    {
      return functionInfo.signature.retType;
    }

    public override int Arity
    {
      get { return functionInfo.signature.argTypes.Length; }
    }

    public override string ToString()
    {
      return FormatAsCall(functionInfo.name);
    }

    // -----------------------------

    public class FunctionInfo
    {
      public readonly String name;
      public readonly MethodInfo methodInfo;
      public readonly Signature signature;
      public readonly bool isSerious;

      private static readonly C5.IDictionary<String, FunctionInfo>
        functions = new HashDictionary<String, FunctionInfo>();

      public FunctionInfo(String name, MethodInfo methodInfo, Signature signature, bool isSerious)
      {
        this.name = name.ToUpper();
        this.methodInfo = methodInfo;
        this.signature = signature;
        functions.Add(this.name, this);
        this.isSerious = isSerious;
      }

      public static bool Find(String name, out FunctionInfo functionInfo)
      {
        return functions.Find(name, out functionInfo);
      }
    }

    // -----------------------------

    public class Signature
    {
      public readonly Typ retType;
      public readonly Typ[] argTypes;

      public static readonly Signature
        unitToNum = new Signature(Typ.Number),
        numToNum = new Signature(Typ.Number, Typ.Number),
        arrayNumNumToValue = new Signature(Typ.Value, Typ.Array, Typ.Number, Typ.Number);

      public Signature(Typ retType, params Typ[] argTypes)
      {
        this.retType = retType;
        this.argTypes = argTypes;
      }
    }
  }

  // ----------------------------------------------------------------

  public class CGSdfCall : CGStrictOperation
  {
    private readonly SdfInfo sdfInfo;
    private bool isInTailPosition = false;

    public CGSdfCall(SdfInfo sdfInfo, CGExpr[] es) : base(es)
    {
      this.sdfInfo = sdfInfo;
    }

    public override void Compile()
    {
      if (es.Length != sdfInfo.Arity)
        LoadError(ErrorValue.argCountErrorValueIndex);
      else
      {
        ilg.Emit(OpCodes.Ldsfld, SdfManager.sdfDelegatesField);
        ilg.Emit(OpCodes.Ldc_I4, sdfInfo.index);
        ilg.Emit(OpCodes.Ldelem_Ref);
        ilg.Emit(OpCodes.Castclass, sdfInfo.myType);
        for (int i = 0; i < es.Length; i++)
          es[i].Compile();
        if (isInTailPosition)
          ilg.Emit(OpCodes.Tailcall);
        ilg.Emit(OpCodes.Call, sdfInfo.myInvoke);
        if (isInTailPosition)
          ilg.Emit(OpCodes.Ret);
      }
    }

    public override void CompileToDoubleOrNan()
    {
      Compile();
      UnwrapToDoubleOrNan();
    }

    public override bool IsSerious(ref int bound)
    {
      return true;
    }

    public override string ToString()
    {
      return FormatAsCall(sdfInfo.name);
    }

    public override void NoteTailPosition()
    {
      isInTailPosition = true;
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      return new StrictCellTypeHolder(false);
    }

    public override Typ Type()
    {
      return Typ.Value;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Value;
    }

    public override int Arity { get { return sdfInfo.Arity; } }
  }

  // ----------------------------------------------------------------

  class CGApply : CGStrictOperation
  {
    //private bool isInTailPosition = false;
    public CGApply(CGExpr[] es) : base(es) { }

    private static readonly MethodInfo
      arrayCopyMethod = typeof(CGApply).GetMethod("ArrayCopy");

    public static Value[] ArrayCopy(Value[] vs)
    {
      Value[] result = new Value[vs.Length];
      // Actually need only copy the last vs.Length-es.Length values
      Array.Copy(vs, result, vs.Length);
      return result;
    }

    public override void Compile()
    {
      if (es.Length < 1)
        LoadError(ErrorValue.argCountErrorValueIndex);
      else
      {
        es[0].Compile();
        CheckType(FunctionValue.type,
          new Generate(delegate
          {
            int arity = es.Length - 1;
            ilg.Emit(OpCodes.Stloc, tmpFunction);
            // Don't check arity here; it is done in the SdfInfo.CallN methods
            ilg.Emit(OpCodes.Ldloc, tmpFunction);
            // Compute and push additional arguments
            for (int i = 1; i < es.Length; i++)
              es[i].Compile();
            // Call the appropriate CallN method on the FunctionValue
            ilg.Emit(OpCodes.Call, FunctionValue.callMethods[arity]);
          }),
        new Generate(delegate { LoadError(ErrorValue.argTypeErrorValueIndex); }));
      }
    }

    public override void CompileToDoubleOrNan()
    {
      Compile();
      UnwrapToDoubleOrNan();
    }

    public override bool IsSerious(ref int bound)
    {
      return true;
    }

    public override string ToString()
    {
      return FormatAsCall("APPLY");
    }

    /*public override void NoteTailPosition()
    {
      isInTailPosition = true;
    }*/

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      switch (pos)
      {
        // Could infer the expected argument types for a more precise function type:
        case 0: return Typ.Function;
        default: return Typ.Value;
      }
    }

    public override int Arity { get { return es.Length; } }

    public override Typ Type()
    {
      // An SDF in general returns a Value
      return Typ.Value;
    }
  }

  // ----------------------------------------------------------------

  class CGExtern : CGStrictOperation
  {
    // If ef==null then errorIndex is set to the error that occurred 
    // during lookup, and the other fields are invalid
    private readonly ExternalFunction ef;
    private readonly int errorIndex;
    private readonly Typ resType;
    private readonly Typ[] argTypes;

    private static readonly C5.ICollection<Type>
      signed32 = new HashSet<Type>(),
      unsigned32 = new HashSet<Type>(),
      numeric = new HashSet<Type>();

    static CGExtern()
    {
      signed32.Add(typeof(System.Int32));
      signed32.Add(typeof(System.Int16));
      signed32.Add(typeof(System.SByte));
      unsigned32.Add(typeof(System.UInt32));
      unsigned32.Add(typeof(System.UInt16));
      unsigned32.Add(typeof(System.Byte));
      numeric.Add(typeof(System.Double));
      numeric.Add(typeof(System.Single));
      numeric.Add(typeof(System.Int64));
      numeric.Add(typeof(System.UInt64));
      numeric.Add(typeof(System.Boolean));
      numeric.AddAll(signed32);
      numeric.AddAll(unsigned32);
    }

    public CGExtern(CGExpr[] es) : base(es) {
      if (es.Length < 1) 
        errorIndex = ErrorValue.argCountErrorValueIndex;
      else
      {
        CGTextConst nameAndSignatureConst = es[0] as CGTextConst;
        if (nameAndSignatureConst == null)
          errorIndex = ErrorValue.argTypeErrorValueIndex;
        else
        {
          try
          {
            // This retrieves the method from cache, or creates it:
            ef = ExternalFunction.Make(nameAndSignatureConst.value.value);
            if (ef.arity != es.Length - 1)
            {
              ef = null;
              errorIndex = ErrorValue.argCountErrorValueIndex;
            }
            else
            {
              resType = FromType(ef.ResType);
              argTypes = new Typ[ef.arity];
              for (int i = 0; i < argTypes.Length; i++)
                argTypes[i] = FromType(ef.ArgType(i));
            }
          }
          catch (Exception exn)  // Covers a multitude of sins
          {
            errorIndex = ErrorValue.GetIndex(exn.Message);
          }
        }
      }    
    }

    private static Typ FromType(Type t)
    {
      if (numeric.Contains(t))
        return Typ.Number;
      else if (t == typeof(System.String))
        return Typ.Text;
      else
        return Typ.Value;
    }

    public override void Compile()
    {
      if (ef == null)
        LoadError(errorIndex);
      else
      {
        OpCode call = ef.isStatic ? OpCodes.Call : OpCodes.Callvirt;
        // If argument evaluation is successful, call the external function 
        // and convert its result to Value; if unsuccessful, return ArgTypeError
        Generate success;
        if (ef.ResType == typeof(System.Double))
          success =
            new Generate(delegate
            {
              ilg.Emit(call, ef.methodInfo);
              ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
            });
        else if (numeric.Contains(ef.ResType))
          success =
            new Generate(delegate
            {
              ilg.Emit(call, ef.methodInfo);
              ilg.Emit(OpCodes.Conv_R8);
              ilg.Emit(OpCodes.Call, NumberValue.makeMethod);
            });
        else if (ef.ResType == typeof(System.String))
          success =
            new Generate(delegate
            {
              ilg.Emit(call, ef.methodInfo);
              ilg.Emit(OpCodes.Call, TextValue.makeMethod);
            });
        else if (ef.ResType == typeof(System.Object))
          success =
            new Generate(delegate
            {
              ilg.Emit(call, ef.methodInfo);
              ilg.Emit(OpCodes.Call, Value.toObjectMethod);
            });
        else if (ef.ResType == typeof(void))
          success =
            new Generate(delegate
            {
              ilg.Emit(call, ef.methodInfo);
              ilg.Emit(OpCodes.Ldsfld, TextValue.voidField);
            });
        else
          throw new Exception("Unhandled return type in EXTERN");
        Generate ifOther = GenLoadError(ErrorValue.argTypeErrorValueIndex);
        CompileArgumentsAndApply(es, success, ifOther);
      }
    }

    public override void CompileToDoubleOrNan()
    {
      if (ef == null)
        ilg.Emit(OpCodes.Ldc_R8, ErrorValue.MakeNan(errorIndex));
      else
      {
        // sestoft: This is maybe correct
        Generate ifOther = new Generate(delegate { LoadArgTypeErrorNan(); });
        OpCode call = ef.isStatic ? OpCodes.Call : OpCodes.Callvirt;
        if (ef.ResType == typeof(System.Double))
        {
          // If argument evaluation is successful, call external function 
          // and continue with ifDouble; otherwise continue with ifOther
          Generate success = new Generate(delegate { ilg.Emit(call, ef.methodInfo); });
          CompileArgumentsAndApply(es, success, ifOther);
        }
        else if (numeric.Contains(ef.ResType))
        {
          // If argument evaluation is successful, call external function, convert 
          // to float64
          Generate success =
            new Generate(delegate
            {
              ilg.Emit(call, ef.methodInfo);
              ilg.Emit(OpCodes.Conv_R8);
            });
          CompileArgumentsAndApply(es, success, ifOther);
        }
        else // Result type cannot be converted to a float64
          ifOther.Gen(ilg);
      }
    }

    // Generate code to evaluate all argument expressions, including the receiver es[1]
    // if the method is an instance method, and convert their values to .NET types.

    private void CompileArgumentsAndApply(CGExpr[] es, Generate ifSuccess, Generate ifOther)
    {
      int argCount = es.Length - 1;
      // The error continuations must pop the arguments computed so far:
      Generate[] errorCont = new Generate[argCount];
      if (argCount > 0)
        errorCont[0] = ifOther;
      for (int i = 1; i < argCount; i++)
      {
        int ii = i; // Capture lvalue -- do NOT inline!
        errorCont[ii] = new Generate(delegate
        {
          ilg.Emit(OpCodes.Pop);
          errorCont[ii - 1].Gen(ilg);
        });
      }
      // Generate code, backwards, to evaluate argument expressions and
      // convert to external method's argument types
      for (int i = argCount - 1; i >= 0; i--)
      {
        // These local vars capture rvalue rather than lvalue -- do NOT inline them!
        CGExpr ei = es[i + 1];
        Generate localSuccess = ifSuccess;
        Type argType = ef.ArgType(i);
        Generate ifError = errorCont[i];
        if (argType == typeof(System.Double))
          ifSuccess = new Generate(
             delegate
             {
               ei.CompileToDoubleOrNan();
               localSuccess.Gen(ilg);
             });
        else if (argType == typeof(System.Single))
          ifSuccess = new Generate(
             delegate
             {
               ei.CompileToDoubleOrNan();
               ilg.Emit(OpCodes.Conv_R4);
               localSuccess.Gen(ilg);
             });
        else if (signed32.Contains(argType))
          ifSuccess = new Generate(
             delegate
             {
               ei.CompileToDoubleProper(
                 new Generate(delegate
               {
                 ilg.Emit(OpCodes.Conv_I4);
                 localSuccess.Gen(ilg);
               }),
                 ifError);
             });
        else if (unsigned32.Contains(argType))
          ifSuccess = new Generate(
             delegate
             {
               ei.CompileToDoubleProper(
                 new Generate(delegate
                 {
                   ilg.Emit(OpCodes.Conv_U4);
                   localSuccess.Gen(ilg);
                 }),
                 ifError);
             });
        else if (argType == typeof(System.Int64))
          ifSuccess = new Generate(
             delegate
             {
               ei.CompileToDoubleProper(
                 new Generate(delegate
                 {
                   ilg.Emit(OpCodes.Conv_I8);
                   localSuccess.Gen(ilg);
                 }),
                 ifError);
             });
        else if (argType == typeof(System.UInt64))
          ifSuccess = new Generate(
             delegate
             {
               ei.CompileToDoubleProper(
                 new Generate(delegate
                 {
                   ilg.Emit(OpCodes.Conv_U8);
                   localSuccess.Gen(ilg);
                 }),
                 ifError);
             });
        else if (argType == typeof(System.Boolean))
          ifSuccess = new Generate(
              delegate
              {
                ei.CompileToDoubleProper(
                   new Generate(delegate
                   {
                     ilg.Emit(OpCodes.Ldc_R8, 0.0);
                     ilg.Emit(OpCodes.Ceq);
                     localSuccess.Gen(ilg);
                   }),
                   ifError);
              });
        else if (argType == typeof(System.String))
          ifSuccess = new Generate(
            delegate
            {
              ei.Compile();
              UnwrapToString(localSuccess, ifError);
            });
        else if (argType == typeof(System.Object)) // And maybe others?
          ifSuccess = new Generate(
            delegate
            {
              ei.Compile();
              ilg.Emit(OpCodes.Call, Value.toObjectMethod);
              localSuccess.Gen(ilg);
            });
        else
          throw new Exception("Unhandled argument type in EXTERN");
      }
      ifSuccess.Gen(ilg);
    }

    public override bool IsSerious(ref int bound)
    {
      return true;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      if (ef == null)
        return Typ.Value;
      switch (pos)
      {
        case 0: 
          return Typ.Text;
        default:
          return argTypes[pos - 1];
      }
    }

    public override int Arity { get { return es.Length; } }

    public override Typ Type()
    {
      if (ef == null)
        return Typ.Value;
      else
        return resType;  // From external function signature
    }
  }

  // ----------------------------------------------------------------

  public class CGNormalCellArea : CGExpr
  {
    private readonly int number;  // If negative, array reference is illegal
    private static readonly MethodInfo getAddressMethod
      = typeof(CGNormalCellArea).GetMethod("GetArrayView");

    // This hashed arraylist maps an ArrayView to its index, which 
    // is used for loading it at runtime
    private static readonly HashedArrayList<ArrayView> cellAreas
      = new HashedArrayList<ArrayView>();

    public CGNormalCellArea(ArrayView array)
    {
      if (array.sheet.IsFunctionSheet)
        number = -1;
      else
      {
        C5.IList<ArrayView> view = cellAreas.ViewOf(array);

        if (view != null)
          number = view.Offset;
        else
        {
          number = cellAreas.Count;
          cellAreas.Add(array);
        }
      }
    }

    public override void Compile()
    {
      if (number >= 0)
      {
        ilg.Emit(OpCodes.Ldc_I4, number);
        ilg.Emit(OpCodes.Call, getAddressMethod);
      }
      else
        LoadError("Range on function sheet");
    }

    public override void CompileToDoubleOrNan()
    {
      LoadArgTypeErrorNan();
    }

    // Used only (through reflection and) from generated code 
    public static ArrayView GetArrayView(int number)
    {
      return cellAreas[number];
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    { }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      // We do not track dependencies on cells or areas on ordinary sheets
    }

    public override Typ Type()
    {
      return Typ.Array;
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      return new StrictCellTypeHolder(false);
    }
  }

  // ----------------------------------------------------------------

  abstract public class CGArithmetic1 : CGStrictOperation
  {
    public CGArithmetic1(CGExpr[] es) : base(es) { }

    public override void Compile()
    {
      CompileToDoubleOrNan();
      WrapDoubleToNumberValue();
    }

    public override Typ Type() { return Typ.Number; }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Number;
    }

    public override int Arity { get { return 1; } }
  }

  // ----------------------------------------------------------------

  public class CGNot : CGArithmetic1
  {
    public CGNot(CGExpr[] es) : base(es) { }

    public override void CompileToDoubleOrNan()
    {
      // sestoft: Seems a bit redundant?
      CompileToDoubleProper(
        new Generate(delegate { }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      // Could compile as follows instead, would sometime be worse, sometimes better:
      //es[0].CompileCondition(ilg, new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 0.0); ifProper.Gen(ilg); }),
      //                            new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, 1.0); ifProper.Gen(ilg); }),
      //                            ifOther);
      if (es.Length != Arity)
      {
        SetArgCountErrorNan();
        ifOther.Gen(ilg);
      }
      else
        es[0].CompileToDoubleProper(
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Ldc_R8, 0.0);
              ilg.Emit(OpCodes.Ceq);
              ilg.Emit(OpCodes.Conv_R8);
              ifProper.Gen(ilg);
            }),
            ifOther);
    }

    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      es[0].CompileCondition(ifFalse, ifTrue, ifOther);
    }

    public override string ToString()
    {
      return FormatAsCall("NOT");
    }
  }

  // ----------------------------------------------------------------

  public class CGNeg : CGArithmetic1
  {
    public CGNeg(CGExpr[] es) : base(es) { }

    public override void CompileToDoubleOrNan()
    {
      es[0].CompileToDoubleOrNan();
      ilg.Emit(OpCodes.Neg);
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      if (es.Length != Arity)
      {
        SetArgCountErrorNan();
        ifOther.Gen(ilg);
      }
      else
        es[0].CompileToDoubleProper(
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Neg);
              ifProper.Gen(ilg);
            }),
            ifOther);
    }

    // -x is true/false/infinite/NaN if and only if x is:
    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      es[0].CompileCondition(ifTrue, ifFalse, ifOther);
    }

    public override string ToString()
    {
      return "-" + es[0];
    }
  }

  // ----------------------------------------------------------------

  // Strict variadic function with return type Number

  public class CGValuesToNumFunction : CGStrictOperation
  {
    private readonly CGFunction.FunctionInfo functionInfo;

    public CGValuesToNumFunction(CGFunction.FunctionInfo functionInfo, CGExpr[] es)
      : base(es)
    {
      this.functionInfo = functionInfo;
    }

    public override void Compile()
    {
      CompileToDoubleOrNan();
      WrapDoubleToNumberValue();
    }

    public override void CompileToDoubleOrNan()
    {
      CreateValueArray(es.Length);
      for (int i = 0; i < es.Length; i++)
      {
        ilg.Emit(OpCodes.Dup);
        ilg.Emit(OpCodes.Ldc_I4, i);
        es[i].Compile();
        ilg.Emit(OpCodes.Stelem_Ref);
      }
      ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
    }

    public override bool IsSerious(ref int bound)
    {
      return functionInfo.isSerious || base.IsSerious(ref bound);
    }

    public override Typ Type()
    {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Value;
    }

    public override int Arity { get { return es.Length; } }

    public override string ToString()
    {
      return FormatAsCall(functionInfo.name);
    }  
  }

  // ----------------------------------------------------------------

  // Strict variadic function with return type Value

  public class CGValuesToValueFunction : CGStrictOperation
  {
    private readonly CGFunction.FunctionInfo functionInfo;

    public CGValuesToValueFunction(CGFunction.FunctionInfo functionInfo, CGExpr[] es)
      : base(es)
    {
      this.functionInfo = functionInfo;
    }

    public override void Compile()
    {
      CompileToValueArray(es.Length, 0, es);
      ilg.Emit(OpCodes.Call, functionInfo.methodInfo);
    }

    public override void CompileToDoubleOrNan()
    {
      Compile();
      UnwrapToDoubleOrNan();
    }

    public override bool IsSerious(ref int bound)
    {
      return functionInfo.isSerious || base.IsSerious(ref bound);
    }

    public override Typ Type()
    {
      return Typ.Value;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Value;
    }

    public override int Arity { get { return es.Length; } }

    public override string ToString()
    {
      return FormatAsCall(functionInfo.name);
    }
  }

  // ----------------------------------------------------------------

  public class CGArithmetic2 : CGStrictOperation
  {
    public readonly OpCode opCode;
    public readonly char op;

    public CGArithmetic2(OpCode opCode, char op, CGExpr[] es)
      : base(es)
    {
      this.opCode = opCode;
      this.op = op;
    }

    public override void Compile()
    {
      CompileToDoubleOrNan();
      WrapDoubleToNumberValue();
    }

    public override void CompileToDoubleOrNan()
    {
      es[0].CompileToDoubleOrNan();
      es[1].CompileToDoubleOrNan();
      ilg.Emit(opCode);
    }

    public override Typ Type()
    {
        return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Number;
    }

    public override int Arity { get { return 2; } }

    public override string ToString()
    {
      if (es.Length == 2)
        return "(" + es[0] + op + es[1] + ")";
      else
        return "Err: ArgCount";
    }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A comparison takes two operands, both numeric for now, and produces
  /// the outcome true (1.0) or false (0.0), or in case either operand 
  /// evaluates to NaN or +/-infinity, it produces that NaN or a plain NaN.
  /// </summary>
  public abstract class CGComparison : CGStrictOperation
  {
    public CGComparison(CGExpr[] es) : base(es) { }

    // These are used in a template method pattern
    // Emit instruction to compare doubles, leaving 1 on stack if true:
    protected abstract void GenCompareDouble();
    // Emit instructions to compare doubles, jump to target if false:
    protected abstract void GenDoubleFalseJump(Label target);
    protected abstract String Name { get; }

    public override void Compile()
    {
      CompileToDoubleProper(new Generate(delegate { WrapDoubleToNumberValue(); }),
                            GenLoadError());
    }

    // A comparison always evaluates to a double; if it cannot evaluate to
    // a proper double, it evaluates to an infinity or NaN.
    // This code seems a bit redundant: It leaves the double on the stack top,
    // whether or not it is proper?  On the other hand, it can be improved only
    // by basically duplicating the CompileToDoubleProper method's body.  
    // Doing so might avoid a jump to a jump or similar, though.
    public override void CompileToDoubleOrNan()
    {
      CompileToDoubleProper(
        new Generate(delegate { }),
        new Generate(delegate { ilg.Emit(OpCodes.Ldloc, testDouble); }));
    }

    // A comparison evaluates to a proper double only if both operands do
    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      es[0].CompileToDoubleProper(
        new Generate(delegate
        {
          es[1].CompileToDoubleProper(
             new Generate(delegate
             {
               GenCompareDouble();
               ilg.Emit(OpCodes.Conv_R8);
               ifProper.Gen(ilg);
             }),
             new Generate(delegate
             {
               ilg.Emit(OpCodes.Pop);
               ifOther.Gen(ilg);
             }));
        }),
        ifOther);
    }

    // This override combines the ordering predicate and the conditional jump
    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      es[0].CompileToDoubleProper(
        new Generate(delegate
        {
          es[1].CompileToDoubleProper(
            new Generate(delegate
            {
              GenDoubleFalseJump(ifFalse.GetLabel(ilg));
              ifTrue.Gen(ilg);
              if (!ifFalse.Generated)
              {
                Label endLabel = ilg.DefineLabel();
                ilg.Emit(OpCodes.Br, endLabel);
                ifFalse.Gen(ilg);
                ilg.MarkLabel(endLabel);
              }
            }),
            new Generate(delegate
            {
              ilg.Emit(OpCodes.Pop);
              ifOther.Gen(ilg);
            }));
        }),
        ifOther);
    }

    public override string ToString()
    {
      return es[0] + Name + es[1];
    }

    public override Typ Type()
    {
      return Typ.Number;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Number;
    }

    public override int Arity { get { return 2; } }
  }

  // ----------------------------------------------------------------

  // Comparisons should also be made to work for strings etc.  Static type
  // information should be exploited when available, and when not, 
  // a general comparer should be generated.  For doubles, we should
  // check that we're consistent with respect to unordered comparison.
  public class CGGreaterThan : CGComparison
  {
    public CGGreaterThan(CGExpr[] es) : base(es) { }

    protected override void GenCompareDouble()
    {
      ilg.Emit(OpCodes.Cgt);
    }

    protected override void GenDoubleFalseJump(Label target)
    {
      ilg.Emit(OpCodes.Ble, target);
    }

    protected override string Name { get { return ">"; } }
  }

  // ----------------------------------------------------------------

  public class CGLessThan : CGComparison
  {
    public CGLessThan(CGExpr[] es) : base(es) { }

    protected override void GenCompareDouble()
    {
      ilg.Emit(OpCodes.Clt);
    }

    protected override void GenDoubleFalseJump(Label target)
    {
      ilg.Emit(OpCodes.Bge, target);
    }

    protected override string Name { get { return "<"; } }
  }

  // ----------------------------------------------------------------

  public class CGLessThanOrEqual : CGComparison
  {
    public CGLessThanOrEqual(CGExpr[] es) : base(es) { }

    protected override void GenCompareDouble()
    {
      // OpCodes.Not negates all int bits and doesn't work here!
      ilg.Emit(OpCodes.Cgt);
      ilg.Emit(OpCodes.Ldc_I4_0);  
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target)
    {
      ilg.Emit(OpCodes.Bgt, target);
    }

    protected override string Name { get { return "<="; } }
  }

  // ----------------------------------------------------------------

  public class CGGreaterThanOrEqual : CGComparison
  {
    public CGGreaterThanOrEqual(CGExpr[] es) : base(es) { }

    protected override void GenCompareDouble()
    {
      ilg.Emit(OpCodes.Clt);
      ilg.Emit(OpCodes.Ldc_I4_0);
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target)
    {
      ilg.Emit(OpCodes.Blt, target);
    }

    protected override string Name { get { return ">="; } }
  }

  // ----------------------------------------------------------------

  public class CGEqual : CGComparison
  {
    public CGEqual(CGExpr[] es) : base(es) { }

    public static CGExpr Make(CGExpr[] es)
    {
      if (es.Length == 2)
      {
        if (IsZero(es[0]))        // 0.0=e1 ==> NOT(e1)
          return new CGNot(new CGExpr[] { es[1] });
        else if (IsZero(es[1]))   // e0=0.0 ==> NOT(e0)
          return new CGNot(new CGExpr[] { es[0] });
      }
      return new CGEqual(es);
    }

    protected override void GenCompareDouble()
    {
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target)
    {
      ilg.Emit(OpCodes.Bne_Un, target);
    }

    protected override string Name { get { return "="; } }
  }

  // ----------------------------------------------------------------

  public class CGNotEqual : CGComparison
  {
    private CGNotEqual(CGExpr[] es) : base(es) { }

    public static CGExpr Make(CGExpr[] es)
    {
      if (es.Length == 2)
      {
        if (IsZero(es[0]))        // 0.0<>e1 ==> AND(e1)
          return new CGAnd(new CGExpr[] { es[1] });
        else if (IsZero(es[1]))   // e0<>0.0 ==> AND(e0)
          return new CGAnd(new CGExpr[] { es[0] });
      }
      return new CGNotEqual(es);
    }

    protected override void GenCompareDouble()
    {
      ilg.Emit(OpCodes.Ceq);
      ilg.Emit(OpCodes.Ldc_I4_0);
      ilg.Emit(OpCodes.Ceq);
    }

    protected override void GenDoubleFalseJump(Label target)
    {
      ilg.Emit(OpCodes.Beq, target);
    }

    protected override string Name { get { return "<>"; } }
  }

  // ----------------------------------------------------------------

  public abstract class CGConst : CGExpr
  {
    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    { }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    { }

    public override StrictCellTypeHolder GetStrictCells()
    {
      return new StrictCellTypeHolder(false);
    }
  }

  // ----------------------------------------------------------------

  public class CGTextConst : CGConst
  {
    public readonly TextValue value;

    public CGTextConst(TextValue value)
    {
      this.value = value;
    }

    public override void Compile()
    {
      if (value.value == "")
        ilg.Emit(OpCodes.Ldsfld, TextValue.emptyField);
      else
      {
        // Would be better to load the existing constant from a global constants table
        ilg.Emit(OpCodes.Ldstr, value.value);
        ilg.Emit(OpCodes.Newobj, TextValue.constructor);
      }
    }

    public override void CompileToDoubleOrNan()
    {
      LoadArgTypeErrorNan();
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      ifOther.Gen(ilg);
    }

    // In Excel, a text used in a conditional produces the error #VALUE! -- mostly
    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      ifOther.Gen(ilg);
    }

    public override Typ Type()
    {
      return Typ.Text;
    }
  }

  // ----------------------------------------------------------------

  public class CGNumberConst : CGConst
  {
    public readonly NumberValue number;

    public CGNumberConst(NumberValue number)
    {
      this.number = number;
    }

    public override void Compile()
    {
      // sestoft: Would be better to load the NumberValue from this instance, 
      // but then it would need to be stored somewhere (e.g. in a global Value
      // array from which it can be loaded).  See notes.txt.
      if (number.value == 0.0)
        ilg.Emit(OpCodes.Ldsfld, NumberValue.zeroField);
      else if (number.value == 1.0)
        ilg.Emit(OpCodes.Ldsfld, NumberValue.oneField);
      else if (number.value == Math.PI)
        ilg.Emit(OpCodes.Ldsfld, NumberValue.piField);
      else
      {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        WrapDoubleToNumberValue();
      }
    }

    public override void CompileToDoubleOrNan()
    {
      ilg.Emit(OpCodes.Ldc_R8, number.value);
    }

    public override void CompileToDoubleProper(Generate ifProper, Generate ifOther)
    {
      if (double.IsInfinity(number.value) || double.IsNaN(number.value))
      {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        ilg.Emit(OpCodes.Stloc, testDouble);
        ifOther.Gen(ilg);
      }
      else
      {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        ifProper.Gen(ilg);
      }
    }

    public override void CompileCondition(Generate ifTrue, Generate ifFalse, Generate ifOther)
    {
      if (Double.IsInfinity(number.value) || Double.IsNaN(number.value))
      {
        ilg.Emit(OpCodes.Ldc_R8, number.value);
        ilg.Emit(OpCodes.Stloc, testDouble);
        ifOther.Gen(ilg);
      }
      else if (number.value != 0)
        ifTrue.Gen(ilg);
      else
        ifFalse.Gen(ilg);
    }

    public override string ToString()
    {
      return number.value.ToString();
    }

    public override Typ Type()
    {
      return Typ.Number;
    }
  }

  // ----------------------------------------------------------------

  public class CGError : CGConst
  {
    private readonly ErrorValue errorValue;

    public CGError(int errorIndex)
    {
      this.errorValue = ErrorValue.errorTable[errorIndex];
    }

    public CGError(String message)
    {
      this.errorValue = ErrorValue.Make(message);
    }

    // This is used to implement the ERR function
    public CGError(CGExpr[] es) {  
      if (es.Length != 1)
        errorValue = ErrorValue.argCountErrorValue;
      else
      {
        CGTextConst messageConst = es[0] as CGTextConst;
        if (messageConst == null)
          errorValue = ErrorValue.argTypeErrorValue;
        else
          errorValue = ErrorValue.Make(messageConst.value.value);
      }    
    }

    public override void Compile()
    {
      LoadError(errorValue.errorIndex);
    }

    public override void CompileToDoubleOrNan()
    {
      ilg.Emit(OpCodes.Ldc_R8, errorValue.ErrorNan);
    }

    public override string ToString()
    {
      return errorValue.message;
    }

    public override Typ Type()
    {
      return Typ.Error;
    }
  }

  // ----------------------------------------------------------------

  public class CGGetfun : CGStrictOperation
  {
    // If sdfIndex < 0 then errorIndex says what went wrong during lookup
    private readonly int sdfIndex;
    private readonly int errorIndex;

    public CGGetfun(CGExpr[] es)
      : base(es)
    {
      this.sdfIndex = -1;
      if (es.Length < 1) {
        this.errorIndex = ErrorValue.argCountErrorValueIndex;
        return;
      }
      CGTextConst nameConst = es[0] as CGTextConst;
      if (nameConst == null)
      {
        this.errorIndex = ErrorValue.argTypeErrorValueIndex;
        return;
      }
      String name = nameConst.value.value;
      SdfInfo sdfInfo = SdfManager.GetInfo(name);
      if (sdfInfo == null)
      {
        this.errorIndex = ErrorValue.noSuchFunctionErrorValueIndex;
        return;
      }
      int givenCount = es.Length - 1;
      if (givenCount > sdfInfo.Arity) 
        this.errorIndex = ErrorValue.argCountErrorValueIndex;
      else
        this.sdfIndex = sdfInfo.index;
    }

    public override void Compile()
    {
      if (sdfIndex < 0)
        LoadError(errorIndex);
      else
      {
        ilg.Emit(OpCodes.Ldc_I4, sdfIndex);
        CompileToValueArray(es.Length-1, 1, es);
        ilg.Emit(OpCodes.Call, FunctionValue.getFunMethod);
      }
    }

    public override int Arity { get { return es.Length; } }

    public override Typ Type()
    {
      return Typ.Function;
    }

    protected override Typ GetInputTypWithoutLengthCheck(int pos)
    {
      return Typ.Value;
    }

    public override void CompileToDoubleOrNan()
    {
      if (sdfIndex < 0)
        LoadError(errorIndex);
      else
        LoadArgTypeErrorNan();
    }
  }

  // ----------------------------------------------------------------

  /// <summary>
  /// Reference to a cell that must be evaluated at runtime; namely, a cell
  /// in an ordinary sheet, not inside a function sheet.
  /// </summary>
  public class CGNormalCellRef : CGExpr
  {
    private readonly int number; // If negative, cell ref is illegal
    private static readonly MethodInfo getAddressMethod
      = typeof(CGNormalCellRef).GetMethod("GetAddress");

    // This hashed arraylist maps a FullCellAddr to its index, which 
    // is used for loading it at runtime
    private static readonly HashedArrayList<FullCellAddr> cellAddresses
      = new HashedArrayList<FullCellAddr>();

    public CGNormalCellRef(FullCellAddr cellAddr)
    {
      if (cellAddr.sheet.IsFunctionSheet)
        number = -1;
      else
      {
        C5.IList<FullCellAddr> view = cellAddresses.ViewOf(cellAddr);
        if (view != null)
          number = view.Offset;
        else
        {
          number = cellAddresses.Count;
          cellAddresses.Add(cellAddr);
        }
      }
    }

    public override void Compile()
    {
      if (number >= 0)
      {
        ilg.Emit(OpCodes.Ldc_I4, number);
        ilg.Emit(OpCodes.Call, getAddressMethod);
        ilg.Emit(OpCodes.Call, FullCellAddr.evalMethod);
      }
      else
        LoadError("Ref to other function sheet");
    }

    public static FullCellAddr GetAddress(int number)
    {
      return cellAddresses[number];
    }

    public override StrictCellTypeHolder GetStrictCells()
    {
      return new StrictCellTypeHolder(false);
    }

    public override void EvalCond(PathCond evalCond, C5.IDictionary<FullCellAddr, PathCond> evalConds)
    { }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      // We do not track dependencies on cells on ordinary sheets
    }

    public override Typ Type()
    {
      // We don't infer cell types in ordinary sheets, so assume the worst
      return Typ.Value;
    }

    public override void CompileToDoubleOrNan()
    {
      Compile();
      UnwrapToDoubleOrNan();
    }
  }

  // ----------------------------------------------------------------
  /// <summary>
  /// A Generate object is a code generator that avoids generating the same code 
  /// multiple times, and also to some extent avoids generating jumps to jumps.  
  /// See notes, section 14.
  /// </summary>

  public class Generate
  {
    private readonly Act generate;
    private Label? label;
    private bool generated;  // Invariant: generated implies label.HasValue

    public Generate(Act generate)
    {
      this.generate = generate;
      label = null;
      generated = false;
    }

    // A Generate object has a unique label
    public Label GetLabel(ILGenerator ilg)
    {
      if (!label.HasValue)
        label = ilg.DefineLabel();
      return label.Value;
    }

    public bool Generated { get { return generated; } }

    public void Gen(ILGenerator ilg)
    {
      if (generated)
        ilg.Emit(OpCodes.Br, GetLabel(ilg));
      else
      {
        ilg.MarkLabel(GetLabel(ilg));
        generated = true;
        generate();
      }
    }
  }
}